package com.citybiz.service.web.service;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.citybiz.common.core.redis.RedisCache;
import com.citybiz.common.exception.WxBizException;
import com.citybiz.common.utils.ServletUtils;
import com.citybiz.common.utils.StringUtils;
import com.citybiz.common.utils.ip.AddressUtils;
import com.citybiz.common.utils.ip.IpUtils;
import com.citybiz.common.utils.uuid.IdUtils;
import com.citybiz.service.web.request.WxLoginRequest;
import com.citybiz.system.domain.WxUser;
import com.citybiz.system.domain.WxUserRole;
import com.citybiz.system.mapper.WxUserPlusMapper;
import com.citybiz.system.mapper.WxUserRolePlusMapper;
import com.citybiz.wxcommon.core.domain.model.WxLoginUser;
import com.citybiz.wxcommon.enums.ApproveStatusEnums;
import com.citybiz.wxcommon.enums.WxUserRoleEnums;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Create by Ykg on 2023/9/29 14:55
 * effect:
 */
@Service
@Slf4j
public class WxLoginService {

    private final WxMaService wxMaService;

    public WxLoginService(WxMaService wxMaService){
        this.wxMaService = wxMaService;
    }

    // 令牌标识
    @Value("${token.header}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期（默认30分钟）
    @Value("${token.expireTime}")
    private int expireTime;

    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    private static final String WX_PREFIX = "wx";

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private WxUserPlusMapper wxUserPlusMapper;

    @Autowired
    private WxUserRolePlusMapper wxUserRolePlusMapper;

    /**
     * 微信登录请求
     * @param request 请求体
     * @return 登录token
     */
    @Transactional
    public String login(WxLoginRequest request){
        WxMaJscode2SessionResult session = null;
        if(request.getCode().equals("123456")) {
            session = new WxMaJscode2SessionResult();
            session.setOpenid("123456");
        }else {
            if (!wxMaService.switchover(request.getAppid())) {
                throw new AccessDeniedException(String.format("未找到对应appid=[%s]的配置，请核实！", request.getAppid()));
            }

            try {
                session = wxMaService.getUserService().getSessionInfo(request.getCode());
                if (session == null) {
                    log.error(String.format("appid=[%s]获取的session为空", request.getAppid()));
                    throw new WxErrorException("登录失败");
                }
            } catch (WxErrorException e) {
                throw new AccessDeniedException("获取微信登录session失败，请确认");
            }
        }
        String openid = session.getOpenid();
        LambdaQueryWrapper<WxUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(WxUser::getOpenId, openid);
        WxUser wxUser = wxUserPlusMapper.selectOne(wrapper);
        if(wxUser == null){
            // 自动注册流程
            wxUser = new WxUser();
            wxUser.setOpenId(openid);
            if(StringUtils.isNotEmpty(request.getPhone())){
                wxUser.setPhone(request.getPhone());
                wxUser.setInitUser(1);
            }else {
                wxUser.setInitUser(0);
            }
            wxUser.setCreateTime(new Date());
            wxUser.setUpdateTime(new Date());
            wxUser.setBalance(new BigDecimal(0));
            wxUser.setCompanyAuth(ApproveStatusEnums.NOT_EXIST.getCode());
            wxUser.setPrivateAuth(ApproveStatusEnums.NOT_EXIST.getCode());
            wxUser.setShopFlag(ApproveStatusEnums.NOT_EXIST.getCode());
            wxUser.setDelFlag(0L);
            wxUserPlusMapper.insert(wxUser);
            WxUserRole worker = new WxUserRole();
            worker.setRole(WxUserRoleEnums.WORKER_ROLE.getCode());
            worker.setRoleName(WxUserRoleEnums.WORKER_ROLE.getMessage());
            worker.setWxUserId(wxUser.getId());
            WxUserRole employer = new WxUserRole();
            employer.setRole(WxUserRoleEnums.EMPLOYER_ROLE.getCode());
            employer.setRoleName(WxUserRoleEnums.EMPLOYER_ROLE.getMessage());
            employer.setWxUserId(wxUser.getId());
            wxUserRolePlusMapper.insert(worker);
            wxUserRolePlusMapper.insert(employer);
        }

        // 获取wxLoginUser
        // WxLoginUser
        LambdaQueryWrapper<WxUserRole> roleWrapper = new LambdaQueryWrapper<>();
        roleWrapper.eq(WxUserRole::getRole, request.getRole())
                .eq(WxUserRole::getWxUserId,wxUser.getId());
        WxLoginUser loginUser = new WxLoginUser();
        loginUser.setNickname(wxUser.getNickname());
        loginUser.setUserId(wxUser.getId());
        loginUser.setLoginTime(new Date().getTime());
        loginUser.setRole(request.getRole());
        loginUser.setOpenId(wxUser.getOpenId());
        return createToken(loginUser);
    }

    /**
     * 切换登录角色
     * @param user 原角色
     * @param role 切换角色
     * @return 切换后的token
     */
    public String changeRole(WxUser user,Integer role){

        WxLoginUser loginUser = new WxLoginUser();
        loginUser.setNickname(user.getNickname());
        loginUser.setUserId(user.getId());
        loginUser.setLoginTime(new Date().getTime());
        loginUser.setRole(role);
        loginUser.setOpenId(user.getOpenId());
        return createToken(loginUser);
    }

    /**
     * 获取用户登录信息
     * @param token 用户令牌
     * @return 微信用户登录信息
     */
    public WxLoginUser getLoginUser(String  token){
        if(StringUtils.isNotEmpty(token)){
            //解析用户信息
            Claims claims = parseToken(token);
            String uuid = (String) claims.get(WX_PREFIX);
            String userKey = getTokenKey(uuid);
            WxLoginUser user = (WxLoginUser) redisCache.getCacheObject(userKey);
            return user;
        }
        return null;
    }

    /**
     * 设置用户身份信息
     * @param wxUSer 微信用户登录信息
     */
    public void setWxUSer(WxLoginUser wxUSer){
        if(StringUtils.isNotNull(wxUSer) && StringUtils.isNotEmpty(wxUSer.getToken())){
            refreshToken(wxUSer);
        }
    }

    /**
     * 删除用户信息
     * @param token 前端传递的令牌
     */
    public void delWxUser(String token){
        if(StringUtils.isNotEmpty(token)){
            Claims claims = parseToken(token);
            String uuid = (String) claims.get(WX_PREFIX);
            String tokenKey = getTokenKey(uuid);
            redisCache.deleteObject(tokenKey);
        }
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     * @param loginUser 用户登录体
     */
    public void verifyToken(WxLoginUser loginUser){
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if(expireTime - currentTime <= MILLIS_MINUTE_TEN){
            refreshToken(loginUser);
        }
    }

    /**
     * 创建令牌
     * @param loginUser 用户登录信息
     * @return 令牌
     */
    public String createToken(WxLoginUser loginUser){
        String uuid = IdUtils.fastUUID();
        loginUser.setToken(uuid);
        setUserAgent(loginUser);
        refreshToken(loginUser);
        Map<String, Object> claims = new HashMap<>();
        claims.put(WX_PREFIX, uuid);
        return createToken(claims);
    }

    private String createToken(Map<String,Object> claims){
        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 设置用户代理信息
     * @param loginUser
     */
    public void setUserAgent(WxLoginUser loginUser){
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
    }

    /**
     * 刷新微信登录令牌有效期
     * @param loginUser 登录信息
     */
    public void refreshToken(WxLoginUser loginUser){
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        //根据userId继续缓存wxLoginUSer
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }


    /**
     * 获取token中的加密信息
     * @param token token
     * @return 加密信息
     */
    private Claims parseToken(String token){
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 解析请求头中的token
     * @param request 请求体
     * @return token
     */
    public String getToken(HttpServletRequest request){
        String token = request.getHeader(header);

        if(StringUtils.isNotEmpty(token)){
            token = token.replace(WX_PREFIX, "");
            return token;
        }
        return null;
    }

    private String getTokenKey(String token){
        return "wx_login" + token;
    }
}
